/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"syscall"

	"gitee.com/chuanjinge/go-container/config"
	"gitee.com/chuanjinge/go-container/container"
	"gitee.com/chuanjinge/go-container/container/containerconfig"
	"gitee.com/chuanjinge/go-container/container/lifecycle"
	"gitee.com/chuanjinge/go-container/container/vethbr"
	"gitee.com/chuanjinge/go-container/option"
	"gitee.com/chuanjinge/go-container/pkg"
	"gitee.com/chuanjinge/go-container/pkg/loggers"
	"github.com/spf13/cobra"
)

var (
	interactive bool
	tty         bool
	memoryLimit int
	cpu         int
	cpuset      int
	volume      []string
	env         []string
	port        []string
	portMap     map[int16]int16
	daemon      bool
	realDaemon  bool
	network     string
	nw          *vethbr.Network
)

// runCmd represents the run command
var runCmd = &cobra.Command{
	Use:   "run [filename]",
	Short: "Run the client with a specified file",
	Long:  `Run the client with a specified file.`,
	Args: func(cmd *cobra.Command, args []string) error {
		if len(args) < 1 {
			return fmt.Errorf("请输出初始镜像名称")
		}

		// 如果network不是空的话，必须检查其是否是已经存在的
		if network != "" {
			nwl, err := vethbr.NewNetworkList()
			if err != nil {
				return fmt.Errorf("获取网络链路出错")
			}
			if nwIstance, exist := nwl.Exist(network); !exist {
				return fmt.Errorf("指定的网络%s不存在", network)
			} else {
				nw = &nwIstance
			}
		}

		// 如果port端口映射不是空，校验其合法性
		if len(port) > 0 {
			portMap = make(map[int16]int16)
			for _, mapping := range port {
				parts := strings.Split(mapping, ":")
				if len(parts) != 2 {
					return fmt.Errorf("invalid port mapping format:%s", mapping)
				}
				num0, err := strconv.Atoi(parts[0])
				if err != nil || num0 < 0 || num0 > 65535 {
					return fmt.Errorf("invalid port number:%s", port)
				}
				num1, err := strconv.Atoi(parts[1])
				if err != nil || num1 < 0 || num1 > 65535 {
					return fmt.Errorf("invalid port number:%s", port)
				}
				portMap[int16(num0)] = int16(num1)
			}
		}

		return nil
	},
	Run: func(cmd *cobra.Command, args []string) {
		// 依赖父进程给子进程设置的环境变量来判断是否是以守护进程的模式在运行
		if os.Getenv("_TINYDOCKER_RUN_MODE") == "daemon" {
			realDaemon = true
		}

		// 参数校验结束之后决定是否执行守护进程的方式
		if daemon && !realDaemon {
			fmt.Printf("[PRE][PREPARE_DAEMON]enter daemon: %v\n", os.Args)
			// 去掉 -d或者--daemon 避免进入创建子进程的死循环之中; 这种方式不适合 -itd 这种联合标识符的状态，所以不依赖了，还是依赖环境变量吧
			os.Args = removeElementInPlace(os.Args, "-d")
			os.Args = removeElementInPlace(os.Args, "--daemon")
			fmt.Printf("[PRE][PREPARE_DAEMON]after remove: %v\n", os.Args)
			daemonize()
		}

		logFile, err := setupLogging(config.GlobalConfig().CommonLogPath)
		if err != nil {
			log.Println("日志文件打开失败")
			os.Exit(1)
		}
		defer logFile.Close()

		if interactive {
			log.Println("开启交互模式")
		}
		if tty {
			log.Println("开启TTY")
		}

		fmt.Printf("portMap: %#v\n", portMap)
		option := &option.CmdOption{
			Interactive: interactive,
			TTY:         tty,
			MemoryLimit: memoryLimit,
			Cpu:         cpu,
			Cpuset:      cpuset,
			Volume:      volume,
			Env:         env,
			Daemon:      realDaemon,
			Network:     nw,
			Port:        portMap,
		}
		containerId := pkg.GetRandom12()
		log.Printf("分配容器ID: %s\n", containerId)
		// 初始化日志记录器
		pkg.InitLoggers(containerId)
		defer func() {
			loggers.CloseAllLogger() // 确保进程结束后关闭日志记录器。
		}()
		// 初始化配置信息
		err = loggers.InitializeLoggerConf(containerconfig.GetConfigLog(containerId))
		if err != nil {
			log.Printf("容器运行时信息日志初始化日常\n")
			os.Exit(1)
		}
		defer loggers.CloseLoggerConf()

		cInfo := container.RunWithNewNamespace(containerId, args, option)

		// 执行到这里，容器前台进程已经退出了
		lifecycle.RemoveContainerNetwork(containerId)
		// 记录容器的运行信息
		cInfo.State.Running = false
		cInfo.State.Paused = true
		pkg.LogConf(cInfo)
		/*
			 * 如下动作应该在rm执行，以便start可以再次唤起在当前目录系统的操作
				log.Println("子进程结束，准备清除工作目录，释放overlay挂载")
				ufs := overlay2.NewUfsDir(containerId)
				if ufs != nil {
					// 先把共享目录卸载掉，要不然overlay应该就卸载不掉了
					ufs.ParseSharedVolume(volume)
					if err := ufs.UnmountSharedVolume(); err != nil {
						log.Println(err.Error())
					}
					if err := ufs.Disapper(); err != nil {
						log.Println(err.Error())
					}
				}
		*/
		log.Println("主进程结束")
		os.Exit(1) // 此处理论上应该根据子进程的信号来决定exit的值
	},
}

// 检查命令行的标志参数 --verbose=true 和 --config=/etc/container.json
func checkFlag() {
	runCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "是否开启交互模式")
	runCmd.Flags().BoolVarP(&tty, "tty", "t", false, "是否启动一个pseudo-TTY")
	runCmd.Flags().IntVarP(&memoryLimit, "memory", "", 0, "容器最大可用内存字节数限制")
	runCmd.Flags().IntVarP(&cpu, "cpu", "", 0, "容器最大可用内存字节数限制")
	runCmd.Flags().IntVarP(&cpuset, "cpuset", "", 0, "容器最大可用内存字节数限制")
	runCmd.Flags().StringSliceVarP(&volume, "volume", "v", nil, "共享挂载卷")
	runCmd.Flags().StringSliceVarP(&env, "env", "e", nil, "环境变量")
	runCmd.Flags().BoolVarP(&daemon, "daemon", "d", false, "以守护进程的方式运行")
	runCmd.Flags().StringVarP(&network, "network", "n", "", "加入指定的网络之中")
	runCmd.Flags().StringSliceVarP(&port, "port", "p", nil, "宿主机到容器的端口映射,eg 8090:80")

	//runCmd.MarkFlagRequired("config") // 必传参数
	//runCmd.MarkFlagDirname("config")  // 可以开启文件名依赖Tab键自动补全的功能
}

func init() {
	checkFlag()
	rootCmd.AddCommand(runCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// runCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// runCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

// 把自己做成守护进程，脱离当前的会话组和进程组
func daemonize() {
	fmt.Printf("[PRE]准备fork子进程作为守护进程\n")
	// fork子进程
	var err error
	_, err = syscall.ForkExec(os.Args[0], os.Args, &syscall.ProcAttr{
		Dir:   ".",
		Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
		Sys: &syscall.SysProcAttr{
			Setsid: true,
		},
		Env: append(os.Environ(), "_TINYDOCKER_RUN_MODE=daemon"),
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("[PRE]进程退出\n")
	os.Exit(0)
}

func removeElementInPlace(slice []string, elem string) []string {
	j := 0
	for _, item := range slice {
		if item != elem {
			slice[j] = item
			j++
		}
	}
	return slice[:j]
}

func contains(s []string, str string) bool {
	for _, v := range s {
		if v == str {
			return true
		}
	}
	return false
}

// SetupLogging sets up logging to file.
// 配置日志记录到文件，以免守护进程的日志文件丢失
func setupLogging(logFile string) (*os.File, error) {
	f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return nil, fmt.Errorf("failed to open log file: %v", err)
	}
	//defer f.Close()
	log.SetOutput(f)
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	return f, nil
}
